#include <iostream>

#include <cv.h>
#include <highgui.h>

#include "resourceTracker.h"
#include "HIString-inl.hpp"

//FIX : big three?

using namespace cv;
using namespace hi;

ResourceTracker::ResourceTracker( const char* cfgXMLFileString ) {

	cfgFile = cvOpenFileStorage(
								cfgXMLFileString,
								0,
								CV_STORAGE_READ
								);
	
	if ( cfgFile == NULL ) {
		CV_Error( CV_StsNullPtr,  "cfg file not found!"); //throws exception!
	}
	
	dataLocationsFile = NULL; //Making sure pointer is not used by mistake before it is properly initialised
	resourceNameString = NULL;
	storage_videoTags = NULL;
	videoTags = NULL;
	
	tagCount = 0;
}

ResourceTracker::~ResourceTracker() {
	
	if ( cfgFile != NULL )
		cvReleaseFileStorage( &cfgFile );
	
	if ( dataLocationsFile != NULL ) {
		cvReleaseFileStorage( &dataLocationsFile );
	}
}

void ResourceTracker::loadCategory( const char* categoryNameString ) {
	
	dataLocationsFileString = cvReadStringByName(
													   cfgFile,
													   0,
													   categoryNameString );
	
	//TODO : exception if category does not exist?
	
	dataLocationsFile = cvOpenFileStorage(
								dataLocationsFileString,
								0,
								CV_STORAGE_READ
								);
	
	dataLocationsPrefixString = cvReadStringByName(
												   dataLocationsFile,
												   0,
												   "prefix"
												   );
	//NOTE does not need separate deallocation
	
	if ( dataLocationsFile == NULL ) {
		CV_Error( CV_StsNullPtr, "Data location file not found!"); //throws exception!
	}
	
}

Mat ResourceTracker::getMat( const char* imageNameString ) {
	
	if ( dataLocationsFile == NULL ) {
		CV_Error( CV_StsNullPtr, "Data location file not set!"); //throws exception!
	}
	
	char* imageLocationString = getResourceLocationString_orphanOut( imageNameString );
	
	Mat img = imread( imageLocationString );
	
	if ( !img.data  ) {
		CV_Error( CV_StsNullPtr, "Data file not found!"); //throws exception!
	}
	
	free(imageLocationString);
	imageLocationString = 0;
	
	return img;
}

Ptr< VideoCapture > ResourceTracker::getFileVideoCapture( const char* videoNameString ) {
	
	if ( dataLocationsFile == NULL ) {
		CV_Error( CV_StsNullPtr, "Data location file not set!"); //throws exception!
	}
	
	char* videoLocationString = getResourceLocationString_orphanOut( videoNameString );
	
	Ptr< VideoCapture > vcPtr = new VideoCapture( videoLocationString );
	//TODO throws what exception?
	
	free(videoLocationString);
	videoLocationString = 0;
	
	return vcPtr;
}

Ptr< VideoPointTagSequence > ResourceTracker::getPointTags( const char* videoNameString ) {
	
	if ( dataLocationsFile == NULL ) {
		CV_Error( CV_StsNullPtr, "Data location file not set!"); //throws exception!
	}
	
	char* tagNameString = getPointTagNameString_orphanOut( videoNameString );
	
	char* tagLocationString = getResourceLocationString_orphanOut( tagNameString );
	
	CvFileStorage* fs = cvOpenFileStorage(
										  tagLocationString,
										  0,
										  CV_STORAGE_READ
										  );
	
	Ptr< VideoPointTagSequence > seqPtr = new VideoPointTagSequence( fs ); 
	
	free(tagLocationString);
	tagLocationString = 0;
	
	free(tagNameString);
	tagNameString = 0;
	
	return seqPtr;
}

Point ResourceTracker::getPointTag( const char* imageNameString ) {
	
	if ( dataLocationsFile == NULL ) {
		CV_Error( CV_StsNullPtr, "Data location file not set!"); //throws exception!
	}
	
	char* tagNameString = getPointTagNameString_orphanOut( imageNameString );

	char* tagLocationString = getResourceLocationString_orphanOut( tagNameString );
	
	CvFileStorage* fs = cvOpenFileStorage(
										  tagLocationString,
										  0,
										  CV_STORAGE_READ
										  );

	CvSeq* seq = (CvSeq*) cvReadByName(
										fs,
										0,
										"point_tag_sequence" //TODO Should be a string const somewhere?
									   );
	
	CvPoint* p1 = CV_GET_SEQ_ELEM( CvPoint, seq, 0 );
	
	// -- Cleaning up orphanOut strings -- //
											
	free(tagNameString);
	tagNameString = 0;	 
																
	free(tagLocationString);
	tagLocationString = 0;
	
	Point p2(p1->x, p1->y);
	
	cvReleaseFileStorage( &fs );
	
	return p2;
}

Ptr< VideoGestureTagSequence > ResourceTracker::getGestureTags( const char* videoNameString ) {
	
	if ( dataLocationsFile == NULL ) {
		CV_Error( CV_StsNullPtr, "Data location file not set!"); //throws exception!
	}
	
	char* gestureTagsNameString = getGestureTagNameString_orphanOut( videoNameString );
	
	char* tagLocationString = getResourceLocationString_orphanOut( gestureTagsNameString );
	
	CvFileStorage* fs = cvOpenFileStorage(
										  tagLocationString,
										  0,
										  CV_STORAGE_READ
										  );
	
	Ptr< VideoGestureTagSequence > seqPtr = new VideoGestureTagSequence( fs ); 
	
	free(tagLocationString);
	tagLocationString = 0;
	
	free(gestureTagsNameString);
	gestureTagsNameString = 0;
	
	return seqPtr;
}


// -- Video Recording Methods -- //
//Currently used by AVIRecorder

void ResourceTracker::startVideoWriter( const char* videoNameString, int captureWidth, int captureHeight, int fps ) {
	
	char* localAVIFileNameString = getLocalAVIFileNameString_orphanOut( videoNameString );
	char* AVIFileNameString = getGlobalFileNameString_orphanOut( localAVIFileNameString );
	
	
	CvSize size = cvSize(
						 captureWidth,
						 captureHeight
						 );
	
	videoWriter = cvCreateVideoWriter(  // Will only work on Linux if you've installed ffmpeg development files correctly,
												  //otherwise segmentation fault.  Windows probably better.
												AVIFileNameString,
												//CV_FOURCC('D', 'I', 'B', ' '), //BROKEN!
												CV_FOURCC('I', '4', '2', '0'),
												//0, // apparently works on Windows, but here? OSX : it seems to default to I420
												//CV_FOURCC('F', 'L', 'V', '1'),
												//CV_FOURCC('D','X','5','0'),    
												fps,
												size
												);
	
	if ( videoWriter == NULL ) {
		CV_Error( CV_StsNullPtr,  "Video writer failed to initialise!"); //throws exception!
	}
	
	setResourceNameString( videoNameString );
	
	// -- Cleaning up adopted strings -- //
	
	free(AVIFileNameString);
	AVIFileNameString = 0;
	
	free(localAVIFileNameString);
	localAVIFileNameString = 0;	
	
}

void ResourceTracker::writeFrameToVideo( IplImage* frame ) {
	if ( videoWriter == NULL ) {
		CV_Error( CV_StsNullPtr,  "Video writer not initialised!"); //throws exception!
	}
	cvWriteToAVI( videoWriter, frame );
}

void ResourceTracker::releaseVideoWriter() {
	
	char* localAVIFileNameString = getLocalAVIFileNameString_orphanOut( resourceNameString );
	
	addResourceFile( resourceNameString, localAVIFileNameString);
	
	if ( videoWriter == NULL ) {
		CV_Error( CV_StsNullPtr,  "Video writer not initialised!"); //throws exception!
	}
	cvReleaseVideoWriter( &videoWriter );
	videoWriter = NULL; // TODO that's done automatically anyway?
					
					
	// -- Cleaning up -- //
					
	free(localAVIFileNameString);
	localAVIFileNameString = 0;
					
	unsetResourceNameString();
}


// -- Video Tagging Methods -- //

void ResourceTracker::startVideoTagging( const char* videoNameString ) {
	
	if ( storage_videoTags != NULL || videoTags != NULL ) {
		CV_Error( CV_StsError, "Attempting to start multiple video tagging sessions!"); //throws exception!
	}
	
	storage_videoTags = cvCreateMemStorage();
	videoTags = cvCreateSeq( CV_SEQ_ELTYPE_POINT | CV_SEQ_KIND_CURVE, sizeof(CvSeq), sizeof(CvPoint), storage_videoTags);

	setResourceNameString( videoNameString );
	
}

void ResourceTracker::tagVideo( int x, int y ) {
	
	CvPoint p = cvPoint(x,y);
	
	cvSeqPush( videoTags, &p ); //store the click location
	
	tagCount++;
}

void ResourceTracker::stopVideoTagging() {
	stopVideoTagging(0,0,0);
}

void ResourceTracker::stopVideoTagging(int firstFrameTagged, int trackingRectWidth, int trackingRectHeight ) {
	
	// -- Storing tag data -- //
	
	char* localTagFileXMLNameString = getLocalTagFileXMLNameString_orphanOut( resourceNameString );
	char* tagFileXMLNameString = getGlobalFileNameString_orphanOut( localTagFileXMLNameString );
	
	if ( this->resourceNameString == NULL ) {
		CV_Error( CV_StsNullPtr, "No video being tagged!"); //throws exception!
	}
	
	
	CvFileStorage* fs = cvOpenFileStorage(
										  tagFileXMLNameString,
										  0,
										  CV_STORAGE_WRITE
										  );
	
	cvWriteInt( fs, "tag_count", tagCount );
	cvWriteInt( fs, "first_frame_tagged", firstFrameTagged );
	cvWriteInt( fs, "track_rect_width", trackingRectWidth );
	cvWriteInt( fs, "track_rect_height", trackingRectHeight );
	cvWrite( fs, "point_tag_sequence", videoTags );
	
	cvReleaseFileStorage( &fs );
	
	// -- Adding path to data locations -- //
	
	char* pointTagNameString = getPointTagNameString_orphanOut( resourceNameString );
	
	addResourceFile( pointTagNameString, localTagFileXMLNameString );
	
	// -- Cleaning up -- //
	
	free(localTagFileXMLNameString);
	localTagFileXMLNameString = 0;
	
	free(tagFileXMLNameString);
	tagFileXMLNameString = 0;
	
	free(pointTagNameString);
	pointTagNameString = 0;
	
	unsetResourceNameString();
}

void ResourceTracker::startVideoTagging( const char* videoNameString, const char* tagListNameString ) {
	
	if ( storage_videoTags != NULL || videoTags != NULL ) {
		CV_Error( CV_StsError, "Attempting to start multiple video tagging sessions!"); //throws exception!
	}
	
	storage_videoTags = cvCreateMemStorage();
	videoTags = cvCreateSeq( CV_SEQ_ELTYPE_POINT | CV_SEQ_KIND_CURVE, sizeof(CvSeq), sizeof(CvPoint), storage_videoTags);
	char* tagAndVideoResourceNameString = strAppend_orphanOut( videoNameString, tagListNameString );
	setResourceNameString( tagAndVideoResourceNameString );
	delete	tagAndVideoResourceNameString;
	
}


// -- Video Gesture Tagging Methods -- //

void ResourceTracker::startVideoGestureTagging( const char* videoNameString ) {
	
	if ( storage_videoTags != NULL || videoTags != NULL ) {
		CV_Error( CV_StsError, "Attempting to start multiple video tagging sessions!"); //throws exception!
	}
	
	storage_videoTags = cvCreateMemStorage();
	videoTags = cvCreateSeq( CV_SEQ_ELTYPE_CODE | CV_SEQ_KIND_CURVE, sizeof(CvSeq), sizeof(char), storage_videoTags);
	
	setResourceNameString( videoNameString );
}

void ResourceTracker::tagVideoHandOpen() {
	char tag = 'o';
	cvSeqPush( videoTags, &tag ); //store the tag
	tagCount++;
}

void ResourceTracker::tagVideoHandClosed() {
	char tag = 'c';
	cvSeqPush( videoTags, &tag ); //store the tag
	tagCount++;
}

void ResourceTracker::stopGestureVideoTagging() {
	
	// -- Storing tag data -- //
	
	char* localTagFileXMLNameString = getLocalGestureTagFileXMLNameString_orphanOut( resourceNameString );
	char* tagFileXMLNameString = getGlobalFileNameString_orphanOut( localTagFileXMLNameString );
	
	if ( this->resourceNameString == NULL ) {
		CV_Error( CV_StsNullPtr, "No video being tagged!"); //throws exception!
	}
	
	
	CvFileStorage* fs = cvOpenFileStorage(
										  tagFileXMLNameString,
										  0,
										  CV_STORAGE_WRITE
										  );
	
	cvWriteInt( fs, "tag_count", tagCount );
	
	cvWrite( fs, "gesture_tag_sequence", videoTags );
	
	cvReleaseFileStorage( &fs );
	
	// -- Adding path to data locations -- //
	
	char* gestureTagNameString = getGestureTagNameString_orphanOut( resourceNameString );
	
	addResourceFile( gestureTagNameString, localTagFileXMLNameString );
	
	// -- Cleaning up -- //
	
	free(localTagFileXMLNameString);
	localTagFileXMLNameString = 0;
	
	free(tagFileXMLNameString);
	tagFileXMLNameString = 0;
	
	free(gestureTagNameString);
	gestureTagNameString = 0;
	
	unsetResourceNameString();
}


// -- Private Methods -- //


// -- Method for adding resource file to data locations -- //

void ResourceTracker::addResourceFile( const char* resourceNameString, const char* localResourceFileName ) {
	
	if ( dataLocationsFile != NULL ) {
		cvReleaseFileStorage( &dataLocationsFile );
	}
	
	dataLocationsFile = cvOpenFileStorage(
										  dataLocationsFileString,
										  0,
										  CV_STORAGE_APPEND
										  );
	
	if ( dataLocationsFile == NULL ) {
		CV_Error( CV_StsNullPtr, "Data location file not accessible!"); //throws exception!
	}
	
	
	//TODO : add info on type of object stored in file?
	cvWriteString( dataLocationsFile, resourceNameString, localResourceFileName);
	
	cvReleaseFileStorage( &dataLocationsFile );
	
	dataLocationsFile = cvOpenFileStorage(
										  dataLocationsFileString,
										  0,
										  CV_STORAGE_READ
										  );
	
	
	if ( dataLocationsFile == NULL ) {
		CV_Error( CV_StsNullPtr, "Data location file not accessible!"); //throws exception!
	}
	
}


//Receiver adopts output char* !
//Receiver must deallocate it.
char* ResourceTracker::getResourceLocationString_orphanOut( const char* resourceNameString ) {
	
	const char* resourceLocalFileName = cvReadStringByName(
														   dataLocationsFile,
														   0,
														   resourceNameString
														   );
	
	if ( resourceLocalFileName == NULL ) {
		char* errString = strAppend_orphanOut( "Resource file not found! ResourceNameString=", resourceNameString );
		CV_Error( CV_StsNullPtr, errString ); //throws exception!
		free(errString);
		errString = 0;
	}
	
	return strAppend_orphanOut( dataLocationsPrefixString, resourceLocalFileName );
}

char* ResourceTracker::getPointTagNameString_orphanOut( const char* resourceNameString ) {
	const char* pointTagSuffixString = "_pointtags";	
	return strAppend_orphanOut( resourceNameString, pointTagSuffixString );
}

char* ResourceTracker::getLocalAVIFileNameString_orphanOut( const char* videoNameString ) {
	const char* AVISuffix = ".avi";
	return strAppend_orphanOut( videoNameString, AVISuffix );
}

char* ResourceTracker::getLocalTagFileXMLNameString_orphanOut( char* resourceNameString ) {
	char* tagSeqXMLSuffix = ".pointtags.xml";
	return strAppend_orphanOut( resourceNameString, tagSeqXMLSuffix );
}

char* ResourceTracker::getLocalGestureTagFileXMLNameString_orphanOut( char* resourceNameString ) {
	char* tagSeqXMLSuffix = ".gesturetags.xml";
	return strAppend_orphanOut( resourceNameString, tagSeqXMLSuffix );
}

char* ResourceTracker::getGestureTagNameString_orphanOut( const char* resourceNameString ) {
	const char* pointTagSuffixString = "_gesturetags";		
	return strAppend_orphanOut( resourceNameString, pointTagSuffixString);
}


//Adds prefix
char* ResourceTracker::getGlobalFileNameString_orphanOut( const char* localFileNameString ) {
	
	//TODO: Don't overallocate!
	char* globalFileNameString = (char*) malloc(strlen(dataLocationsPrefixString) + strlen(localFileNameString) + 1);
	
	strcpy(globalFileNameString, dataLocationsPrefixString);
	strcat(globalFileNameString, localFileNameString);
	
	return globalFileNameString;
}


//WARNING! Add checks to make sure it's used properly!
void ResourceTracker::setResourceNameString( const char* resourceNameString ) {
	
	if ( this->resourceNameString != NULL ) {
		CV_Error( CV_StsError, "Attempting to set data file when one is already set!"); //throws exception!
	}
	
	this->resourceNameString = (char*) malloc(strlen(resourceNameString) + 1); //TODO test buffer correct?
	strcpy(this->resourceNameString, resourceNameString);
}

void ResourceTracker::unsetResourceNameString() {
	if ( this->resourceNameString == NULL ) {
		CV_Error( CV_StsNullPtr, "Attempting to free data name string when it is not set!"); //throws exception!
	}
	
	free(this->resourceNameString);
	this->resourceNameString = 0;
}
								
									